package de.fhkl.gatav.ut.localimageops_1;

import java.util.LinkedList;
import android.content.Context;
import android.util.AttributeSet;
import android.util.Log;
import android.view.MotionEvent;
import android.view.SurfaceHolder.Callback;
import android.view.View;
import android.view.View.OnTouchListener;
import android.view.SurfaceView;
import android.view.SurfaceHolder;
import android.graphics.Canvas;
import android.graphics.Paint;
import android.graphics.Bitmap;
import android.graphics.PointF;
import android.graphics.Rect;
import android.graphics.RectF;

/****
 * bubblesView: Manages display handling of app. Implements SurfaceHolder.Callback to access certain display properties
 */
public class ImageWorkbenchView extends SurfaceView implements SurfaceHolder.Callback, OnTouchListener {

	public final static int MODE_BRIGHT = 1;
	public final static int MODE_CONTRAST = 2;
	public final static int MODE_BLUR = 3;
	public final static int MODE_SHARPEN = 4;
	
	private SurfaceHolder surfaceHolder = null;		//Surface to hijack
	private GameLoop gameLoop;						//Display refresh thread
	private Bitmap workBitmap = null;
	private Bitmap displayBitmap = null;
	private float newAtX, newAtY, oldAtX, oldAtY; 
	private LinkedList<PointF> touchPoints = new LinkedList<PointF>();
	private int mode = MODE_BRIGHT;
	
/****
 * Constructor
 * @param context
 * @param attrs
 */
	public ImageWorkbenchView(Context context, AttributeSet attrs) {
		super(context, attrs);
		getHolder().addCallback((Callback) this);	//Register this class as callback handler for the surface
	}

	@Override
	public boolean onTouch( View v, MotionEvent event ) {
		int action = event.getAction();
		switch (action) {
		case MotionEvent.ACTION_DOWN:
			oldAtX = -1.0f;
			oldAtY = -1.0f;
			newAtX = event.getX();
			newAtY = event.getY();
			if(newAtX!=oldAtX && newAtY!=oldAtY) {
				oldAtX = newAtX;
				oldAtY = newAtY;
//				Log.v("DOWN", newAtX+" "+newAtY);
			}			
			break;
			
		case MotionEvent.ACTION_MOVE:
			newAtX = event.getX();
			newAtY = event.getY();
			if(newAtX!=oldAtX && newAtY!=oldAtY) {
				oldAtX = newAtX;
				oldAtY = newAtY;
				synchronized (touchPoints) {
					touchPoints.addFirst(new PointF(newAtX, newAtY));
				}
//				Log.v("MOVE", newAtX+" "+newAtY);
			}
			break;
			
		case MotionEvent.ACTION_UP:
			newAtX = event.getX();
			newAtY = event.getY();
			if(newAtX!=oldAtX && newAtY!=oldAtY) {
				oldAtX = -1.0f;
				oldAtY = -1.0f;
				synchronized (touchPoints) {
					touchPoints.addFirst(new PointF(newAtX, newAtY));
				}
//				Log.v("UP",newAtX+" "+newAtY); 
			}
			break;
			
		default:
			return false;
		}

		return true;
	}
	
/****
 * 	
 */
	public void setImage(Bitmap b) {
		workBitmap = b.copy(Bitmap.Config.ARGB_8888, false);
		displayBitmap = b.copy(Bitmap.Config.ARGB_8888, true);

		this.setOnTouchListener(this);
	}
	
/****
 * 	
 */
	public Bitmap getImage() {
		Bitmap resultBmp = displayBitmap.copy(Bitmap.Config.ARGB_8888, false);

		workBitmap = null;		
		displayBitmap = null;
		touchPoints.removeAll(touchPoints);
		this.setOnTouchListener(null);
		
		return resultBmp;
	}	
/****
 * 	
 */
	public void setMode(int m) {
		if (m == MODE_BRIGHT || m == MODE_CONTRAST || m == MODE_BLUR || m == MODE_SHARPEN) {
			mode = m;
			synchronized (touchPoints) {
				touchPoints.removeAll(touchPoints);
			}

		}
	}
	
/****
 * drawScreen: Paints background and all bubbles	
 * @param c: Canvas to be drawn on
 */
	private void drawScreen(Canvas c) {
		Rect nullRect = null;
		Paint nullPaint = null;
		if (displayBitmap != null) {
			c.drawBitmap(displayBitmap, nullRect, 
						 new RectF(0,0,c.getWidth(),c.getHeight()), 
						 nullPaint);
		}
	}

/****
 * calculateDisplay: Generates new bubble, moves bubble, removes unused bubbles	
 * @param canvas: Canvas to calculate for
 */
	private void calculateDisplay(Canvas canvas) {
		PointF screenPoint;

		if (!touchPoints.isEmpty()) {
			synchronized (touchPoints) {
				screenPoint = touchPoints.removeLast();
			}
		
			switch (mode) {
			case MODE_BRIGHT:
				bright(canvas, screenPoint);
				break;
			case MODE_CONTRAST:
				contrast(canvas, screenPoint);
				break;
			case MODE_BLUR:
				blur(canvas, screenPoint);
				break;
			case MODE_SHARPEN:
				sharpen(canvas, screenPoint);
				break;
			default:
				break;
			}
		}				
	}

/****
* bright: Generates new brightness manipulated display bitmap	
* @param canvas: Canvas to calculate for
*/
	private void bright(Canvas canvas, PointF screenPoint) {
		PointF bitmapPoint = new PointF();
		float bitmapCanvasXRatio;
		float bitmapCanvasYRatio;
		int roiHalfSize = 5;
		int offset = 100;
		int argb1, tr1, tg1, tb1;
		int argb2, ta2, tr2, tg2, tb2;
		int argb3, ta3, tr3, tg3, tb3;
		int alpha = 50;
		int oneMinusAlpha = 100-alpha;
			
		if ((workBitmap != null) && (displayBitmap != null)) {
//			Log.v("CALC-W", workBitmap.getWidth()+" "+workBitmap.getHeight());
//			Log.v("CALC-C", canvas.getWidth()+" "+canvas.getHeight());
			
			bitmapCanvasXRatio = ((float) workBitmap.getWidth()) / canvas.getWidth();
			bitmapCanvasYRatio = ((float) workBitmap.getHeight()) / canvas.getHeight();
//			Log.v("CALC-S", screenPoint.x+" "+screenPoint.y);
			
			bitmapPoint.x = screenPoint.x * bitmapCanvasXRatio;
			bitmapPoint.y = screenPoint.y * bitmapCanvasYRatio;
//			Log.v("CALC-B", bitmapPoint.x+" "+bitmapPoint.y);
			
			int roiUpperLeftX = (int) Math.max(bitmapPoint.x-roiHalfSize, 0);
			int roiUpperLeftY = (int) Math.max(bitmapPoint.y-roiHalfSize, 0);
			int roiLowerRightX = (int) Math.min(bitmapPoint.x+roiHalfSize, workBitmap.getWidth());
			int roiLowerRightY = (int) Math.min(bitmapPoint.y+roiHalfSize, workBitmap.getHeight());
			
			for (int i=roiUpperLeftX; i<roiLowerRightX; i++) {
				for (int j=roiUpperLeftY; j<roiLowerRightY; j++) {
					tr1 = offset; tg1 = offset; tb1 = offset;
					argb1 = workBitmap.getPixel(i, j);
	                tr1 += (argb1 >> 16) & 0xff; tr1 = (tr1>255)?(255):((tr1<0)?(0):(tr1));
	                tg1 += (argb1 >> 8) & 0xff; tg1 = (tg1>255)?(255):((tg1<0)?(0):(tg1));
	                tb1 += argb1 & 0xff; tb1 = (tb1>255)?(255):((tb1<0)?(0):(tb1));

	                argb2 = displayBitmap.getPixel(i, j);
	                ta2 = (argb2 >> 24) & 0xff;
	                tr2 = (argb2 >> 16) & 0xff;
	                tg2 = (argb2 >> 8) & 0xff;
	                tb2 = argb2 & 0xff;

	                ta3 = ta2;
	                tr3 = (oneMinusAlpha*tr2 + alpha*tr1) / 100;
	                tg3 = (oneMinusAlpha*tg2 + alpha*tg1) / 100;
	                tb3 = (oneMinusAlpha*tb2 + alpha*tb1) / 100;
	                argb3 = (ta3 << 24) | (tr3 << 16) | (tg3 << 8) | tb3;

	                displayBitmap.setPixel(i, j, argb3);
				}
			}
		}
	}
	
/****
 * contrast: Generates new contrast manipulated display bitmap	
 * @param canvas: Canvas to calculate for
 */	
	private void contrast(Canvas canvas, PointF screenPoint) {
		PointF bitmapPoint = new PointF();
		float bitmapCanvasXRatio;
		float bitmapCanvasYRatio;
		int roiHalfSize = 5;
		float contrast = 1.4f;
		float trf, tgf, tbf;
		int argb1, tr1, tg1, tb1;
		int argb2, ta2, tr2, tg2, tb2;
		int argb3, ta3, tr3, tg3, tb3;
		int alpha = 50;
		int oneMinusAlpha = 100-alpha;
		
		if ((workBitmap != null) && (displayBitmap != null)) {
//			Log.v("CALC-W", workBitmap.getWidth()+" "+workBitmap.getHeight());
//			Log.v("CALC-C", canvas.getWidth()+" "+canvas.getHeight());
		
			bitmapCanvasXRatio = ((float) workBitmap.getWidth()) / canvas.getWidth();
			bitmapCanvasYRatio = ((float) workBitmap.getHeight()) / canvas.getHeight();
//			Log.v("CALC-S", screenPoint.x+" "+screenPoint.y);
			
			bitmapPoint.x = screenPoint.x * bitmapCanvasXRatio;
			bitmapPoint.y = screenPoint.y * bitmapCanvasYRatio;
//			Log.v("CALC-B", bitmapPoint.x+" "+bitmapPoint.y);
			
			int roiUpperLeftX = (int) Math.max(bitmapPoint.x-roiHalfSize, 0);
			int roiUpperLeftY = (int) Math.max(bitmapPoint.y-roiHalfSize, 0);
			int roiLowerRightX = (int) Math.min(bitmapPoint.x+roiHalfSize, workBitmap.getWidth());
			int roiLowerRightY = (int) Math.min(bitmapPoint.y+roiHalfSize, workBitmap.getHeight());
			
			for (int i=roiUpperLeftX; i<roiLowerRightX; i++) {
				for (int j=roiUpperLeftY; j<roiLowerRightY; j++) {
					trf = contrast; tgf = contrast; tbf = contrast;
					argb1 = workBitmap.getPixel(i, j);
	                trf *= (argb1 >> 16) & 0xff; tr1 = (int)trf; tr1 = (tr1>255)?(255):((tr1<0)?(0):(tr1));
	                tgf *= (argb1 >> 8) & 0xff; tg1 = (int)tgf; tg1 = (tg1>255)?(255):((tg1<0)?(0):(tg1));
	                tbf *= argb1 & 0xff; tb1 = (int)tbf; tb1 = (tb1>255)?(255):((tb1<0)?(0):(tb1));

	                argb2 = displayBitmap.getPixel(i, j);
	                ta2 = (argb2 >> 24) & 0xff;
	                tr2 = (argb2 >> 16) & 0xff;
	                tg2 = (argb2 >> 8) & 0xff;
	                tb2 = argb2 & 0xff;

	                ta3 = ta2;
	                tr3 = (oneMinusAlpha*tr2 + alpha*tr1) / 100;
	                tg3 = (oneMinusAlpha*tg2 + alpha*tg1) / 100;
	                tb3 = (oneMinusAlpha*tb2 + alpha*tb1) / 100;
	                argb3 = (ta3 << 24) | (tr3 << 16) | (tg3 << 8) | tb3;

	                displayBitmap.setPixel(i, j, argb3);
				}
			}
		}
	}
	
/****
* blur: Generates new blurred display bitmap	
* @param canvas: Canvas to calculate for
*/	
	private void blur(Canvas canvas, PointF screenPoint) {
		PointF bitmapPoint = new PointF();
		float bitmapCanvasXRatio;
		float bitmapCanvasYRatio;
		int roiHalfSize = 5;
		int opElements = 0;
		int opHalfSize = 3;
		int argb1, tr1, tg1, tb1;
		int argb2, ta2, tr2, tg2, tb2;
		int argb3, ta3, tr3, tg3, tb3;
		int alpha = 30;
		int oneMinusAlpha = 100-alpha;
		
		if ((workBitmap != null) && (displayBitmap != null)) {
//			Log.v("CALC-W", workBitmap.getWidth()+" "+workBitmap.getHeight());
//			Log.v("CALC-C", canvas.getWidth()+" "+canvas.getHeight());
			
			bitmapCanvasXRatio = ((float) workBitmap.getWidth()) / canvas.getWidth();
			bitmapCanvasYRatio = ((float) workBitmap.getHeight()) / canvas.getHeight();
//			Log.v("CALC-S", screenPoint.x+" "+screenPoint.y);
			
			bitmapPoint.x = screenPoint.x * bitmapCanvasXRatio;
			bitmapPoint.y = screenPoint.y * bitmapCanvasYRatio;
//			Log.v("CALC-B", bitmapPoint.x+" "+bitmapPoint.y);
			
			int roiUpperLeftX = (int) Math.max(bitmapPoint.x-roiHalfSize, 0);
			int roiUpperLeftY = (int) Math.max(bitmapPoint.y-roiHalfSize, 0);
			int roiLowerRightX = (int) Math.min(bitmapPoint.x+roiHalfSize, workBitmap.getWidth());
			int roiLowerRightY = (int) Math.min(bitmapPoint.y+roiHalfSize, workBitmap.getHeight());
			
			for (int i=roiUpperLeftX; i<roiLowerRightX; i++) {
				for (int j=roiUpperLeftY; j<roiLowerRightY; j++) {
					int upperLeftX = (int) Math.max(i-opHalfSize, roiUpperLeftX);
					int upperLeftY = (int) Math.max(j-opHalfSize, roiUpperLeftY);
					int lowerRightX = (int) Math.min(i+opHalfSize, roiLowerRightX);
					int lowerRightY = (int) Math.min(j+opHalfSize, roiLowerRightY);
					tr1 = 0; tg1 = 0; tb1 = 0; opElements = 0;
					for (int k=upperLeftX; k<lowerRightX; k++) {
						for (int l=upperLeftY; l<lowerRightY; l++) {
							argb1 = workBitmap.getPixel(k,l);
							tr1 += (argb1 >> 16) & 0xff;
							tg1 += (argb1 >> 8) & 0xff;
							tb1 += argb1 & 0xff;
							opElements++;							
						}
					}
					tr1 /= opElements;
					tg1 /= opElements;
					tb1 /= opElements;
					
					argb2 = displayBitmap.getPixel(i, j);
					ta2 = (argb2 >> 24) & 0xff;
					tr2 = (argb2 >> 16) & 0xff;
					tg2 = (argb2 >> 8) & 0xff;
					tb2 = argb2 & 0xff;
					
					ta3 = ta2;
					tr3 = (oneMinusAlpha*tr2 + alpha*tr1) / 100;
					tg3 = (oneMinusAlpha*tg2 + alpha*tg1) / 100;
					tb3 = (oneMinusAlpha*tb2 + alpha*tb1) / 100;
					
					argb3 = (ta3 << 24) | (tr3 << 16) | (tg3 << 8) | tb3;
					displayBitmap.setPixel(i, j, argb3);					
				}
			}
		}
	}
	
/****
 * sharpen: Generates new sharpened display bitmap	
 * @param canvas: Canvas to calculate for
 */
//TO BE DONE	
	private void sharpen(Canvas canvas, PointF screenPoint) {
		PointF bitmapPoint = new PointF();
		float bitmapCanvasXRatio;
		float bitmapCanvasYRatio;
		int roiHalfSize = 5;
		int opElements = 0;
		int opHalfSize = 3;
		int argb1, tr1, tg1, tb1;
		int argb2, ta2, tr2, tg2, tb2;
		int argb3, ta3, tr3, tg3, tb3;
		int argb4, tr4, tg4, tb4;
		int weight = 3;
		int alpha = 70;
		int oneMinusAlpha = 100-alpha;
		
		if ((workBitmap != null) && (displayBitmap != null)) {
//			Log.v("CALC-W", workBitmap.getWidth()+" "+workBitmap.getHeight());
//			Log.v("CALC-C", canvas.getWidth()+" "+canvas.getHeight());
			
			bitmapCanvasXRatio = ((float) workBitmap.getWidth()) / canvas.getWidth();
			bitmapCanvasYRatio = ((float) workBitmap.getHeight()) / canvas.getHeight();
//			Log.v("CALC-S", screenPoint.x+" "+screenPoint.y);
			
			bitmapPoint.x = screenPoint.x * bitmapCanvasXRatio;
			bitmapPoint.y = screenPoint.y * bitmapCanvasYRatio;
//			Log.v("CALC-B", bitmapPoint.x+" "+bitmapPoint.y);
			
			int roiUpperLeftX = (int) Math.max(bitmapPoint.x-roiHalfSize, 0);
			int roiUpperLeftY = (int) Math.max(bitmapPoint.y-roiHalfSize, 0);
			int roiLowerRightX = (int) Math.min(bitmapPoint.x+roiHalfSize, workBitmap.getWidth());
			int roiLowerRightY = (int) Math.min(bitmapPoint.y+roiHalfSize, workBitmap.getHeight());
			
			for (int i=roiUpperLeftX; i<roiLowerRightX; i++) {
				for (int j=roiUpperLeftY; j<roiLowerRightY; j++) {
					int upperLeftX = (int) Math.max(i-opHalfSize, roiUpperLeftX);
					int upperLeftY = (int) Math.max(j-opHalfSize, roiUpperLeftY);
					int lowerRightX = (int) Math.min(i+opHalfSize, roiLowerRightX);
					int lowerRightY = (int) Math.min(j+opHalfSize, roiLowerRightY);
					tr1 = 0; tg1 = 0; tb1 = 0; opElements = 0;
					for (int k=upperLeftX; k<lowerRightX; k++) {
						for (int l=upperLeftY; l<lowerRightY; l++) {
							argb1 = workBitmap.getPixel(k,l);
							tr1 += (argb1 >> 16) & 0xff;
							tg1 += (argb1 >> 8) & 0xff;
							tb1 += argb1 & 0xff;
							opElements++;							
						}
					}
					tr1 /= opElements;
					tg1 /= opElements;
					tb1 /= opElements;
					
					argb4 = workBitmap.getPixel(i, j);
					tr4 = (argb4 >> 16) & 0xff;
					tg4 = (argb4 >> 8) & 0xff;
					tb4 = argb4 & 0xff;

					tr4 += weight*(tr4 - tr1); tr4 = (tr4>255)?(255):((tr4<0)?(0):(tr4));
					tg4 += weight*(tg4 - tg1); tg4 = (tg4>255)?(255):((tg4<0)?(0):(tg4));
					tb4 += weight*(tb4 - tb1); tb4 = (tb4>255)?(255):((tb4<0)?(0):(tb4));
					
					argb2 = displayBitmap.getPixel(i, j);
					ta2 = (argb2 >> 24) & 0xff;
					tr2 = (argb2 >> 16) & 0xff;
					tg2 = (argb2 >> 8) & 0xff;
					tb2 = argb2 & 0xff;
					
					ta3 = ta2;
					tr3 = (oneMinusAlpha*tr2 + alpha*tr4) / 100;
					tg3 = (oneMinusAlpha*tg2 + alpha*tg4) / 100;
					tb3 = (oneMinusAlpha*tb2 + alpha*tb4) / 100;
					
					argb3 = (ta3 << 24) | (tr3 << 16) | (tg3 << 8) | tb3;
					displayBitmap.setPixel(i, j, argb3);					
				}
			}
		}
	}
	
/****
 * Private display loop thread	
 */
	private class GameLoop extends Thread {
		public boolean running = true;		//Control flag for start / stop mechanism

/****
 * run is the standard routine called, when a thread is started via the start() method	
 */		
		public void run() {
			Canvas canvas = null;
			final SurfaceHolder surfaceHolder = ImageWorkbenchView.this.surfaceHolder;
			
			while (running) {
				try {
					canvas = surfaceHolder.lockCanvas();	//Get the canvas exclusively
					synchronized (surfaceHolder) {			//Must be executed exclusively
						drawScreen(canvas);					//Draw bitmap
					}
					calculateDisplay(canvas);				//Make an iteration
				} finally {
					if (canvas != null)
						surfaceHolder.unlockCanvasAndPost(canvas);
				}
			}
		}		
	}
		      			
/****
 * Interfcae implementation	
 */
	
/****
 * Called when display is up	
 */
	public void surfaceCreated(SurfaceHolder holder) {
		surfaceHolder = holder;
		synchronized (this) {				//Must be executed exclusively
			if (gameLoop == null) {
				gameLoop = new GameLoop();	//Start animation here
				gameLoop.start();
			}
		}
	}
	
/****
 * Not used	
 */
	public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {
		//Nothing to do
	}
	
/****
 * Called before display will be brought down	
 */
	public void surfaceDestroyed(SurfaceHolder holder) {
		synchronized (this) {				//Must be executed exclusively
			boolean retry = true;
			if (gameLoop != null) {			//Stop the loop
				gameLoop.running = false;
				while (retry) {
					try {
						gameLoop.join();	//Catch the thread
						retry = false;
					} catch (InterruptedException e) {
					}
				}
			}
			gameLoop = null;
		}
	}
}